<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Cesium + Three.js PCD点云渲染</title>
    
    <!-- 引入Cesium -->
    <link href="https://cesium.com/downloads/cesiumjs/releases/1.107/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
    <script src="https://cesium.com/downloads/cesiumjs/releases/1.107/Build/Cesium/Cesium.js"></script>
    
    <!-- 引入Three.js -->
    <!-- <script src="http://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
    <script src="http://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/PCDLoader.js"></script> -->
    <script src="./three.min.js"></script>
    <script src="./PCDLoader.js"></script>

    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            overflow: hidden;
            background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
            color: #fff;
        }
        
        #cesiumContainer {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
        }
        
        .header {
            position: absolute;
            top: 20px;
            left: 0;
            width: 100%;
            text-align: center;
            z-index: 100;
            pointer-events: none;
        }
        
        .header h1 {
            font-size: 2.8rem;
            text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
            margin-bottom: 10px;
            background: linear-gradient(to right, #4facfe, #00f2fe);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
        }
        
        .header p {
            font-size: 1.2rem;
            max-width: 800px;
            margin: 0 auto;
            padding: 0 20px;
            text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
            color: #e0e0ff;
        }
        
        .control-panel {
            position: absolute;
            top: 20px;
            right: 20px;
            background: rgba(10, 20, 30, 0.85);
            border-radius: 15px;
            padding: 20px;
            width: 300px;
            z-index: 100;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
            border: 1px solid rgba(100, 150, 255, 0.3);
            backdrop-filter: blur(10px);
        }
        
        .control-panel h2 {
            margin-bottom: 20px;
            font-size: 1.5rem;
            color: #4facfe;
            text-align: center;
            padding-bottom: 10px;
            border-bottom: 1px solid rgba(100, 150, 255, 0.3);
        }
        
        .control-group {
            margin-bottom: 20px;
        }
        
        .control-group h3 {
            margin-bottom: 10px;
            font-size: 1.1rem;
            color: #00f2fe;
        }
        
        .slider-container {
            display: flex;
            align-items: center;
            margin-bottom: 10px;
        }
        
        .slider-container label {
            width: 120px;
            font-size: 0.9rem;
        }
        
        input[type="range"] {
            flex: 1;
            height: 6px;
            border-radius: 3px;
            background: #2c3e50;
            outline: none;
            -webkit-appearance: none;
        }
        
        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 18px;
            height: 18px;
            border-radius: 50%;
            background: #4facfe;
            cursor: pointer;
        }
        
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(to right, #4facfe, #00f2fe);
            border: none;
            border-radius: 8px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            transition: all 0.3s ease;
            margin-top: 10px;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
        }
        
        button:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 15px rgba(0, 0, 0, 0.4);
        }
        
        button:active {
            transform: translateY(1px);
        }
        
        .info-panel {
            position: absolute;
            bottom: 20px;
            left: 20px;
            background: rgba(10, 20, 30, 0.85);
            border-radius: 15px;
            padding: 20px;
            width: 350px;
            z-index: 100;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
            border: 1px solid rgba(100, 150, 255, 0.3);
            backdrop-filter: blur(10px);
        }
        
        .info-panel h2 {
            margin-bottom: 15px;
            font-size: 1.5rem;
            color: #4facfe;
            text-align: center;
        }
        
        .info-content {
            line-height: 1.6;
        }
        
        .info-content p {
            margin-bottom: 10px;
            font-size: 0.95rem;
        }
        
        .point-count {
            display: inline-block;
            padding: 3px 8px;
            background: rgba(79, 172, 254, 0.2);
            border-radius: 4px;
            font-weight: bold;
        }
        
        .loading {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.7);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 1000;
            flex-direction: column;
        }
        
        .spinner {
            width: 60px;
            height: 60px;
            border: 5px solid rgba(255, 255, 255, 0.1);
            border-radius: 50%;
            border-top: 5px solid #4facfe;
            animation: spin 1s linear infinite;
            margin-bottom: 20px;
        }
        
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
        
        .loading p {
            font-size: 1.2rem;
            color: white;
        }
        
        .toggle-panel {
            position: absolute;
            top: 20px;
            left: 20px;
            z-index: 100;
        }
        
        .toggle-btn {
            padding: 10px 15px;
            background: rgba(10, 20, 30, 0.85);
            border: 1px solid rgba(100, 150, 255, 0.3);
            color: white;
            border-radius: 8px;
            cursor: pointer;
            font-weight: bold;
        }
        
        .hidden {
            display: none;
        }
    </style>
</head>
<body>
    <div id="cesiumContainer"></div>
    
    <div class="header">
        <!-- <h1>Cesium + Three.js PCD点云渲染</h1> -->
        <!-- <p>集成地理空间可视化与点云渲染技术，展示真实世界中的三维点云数据</p> -->
    </div>
    
    <div class="toggle-panel">
        <button class="toggle-btn" id="toggleControls">显示控制面板</button>
    </div>
    
    <div class="control-panel hidden" id="controlPanel">
        <h2>点云控制面板</h2>
        
        <div class="control-group">
            <h3>点云设置</h3>
            <div class="slider-container">
                <label>点大小:</label>
                <input type="range" id="pointSize" min="1" max="20" value="1" step="1">
            </div>
        </div>
        
        <div class="control-group">
            <h3>坐标转换</h3>
            <div class="slider-container">
                <label>模型缩放比例:</label>
                <input type="range" id="pcdScale" min="1" max="10000" value="1000" step="100">
            </div>
            <div class="slider-container">
                <label>经度偏移:</label>
                <input type="range" id="longitudeOffset" min="-1" max="1" value="0" step="0.001">
            </div>
            <div class="slider-container">
                <label>纬度偏移:</label>
                <input type="range" id="latitudeOffset" min="-1" max="1" value="0" step="0.001">
            </div>
            <div class="slider-container">
                <label>高度偏移:</label>
                <input type="range" id="heightOffset" min="1" max="100000" value="1" step="10">
            </div>
        </div>
        
        <div class="control-group">
            <h3>点云操作</h3>
            <button id="loadPCDFile">重新渲染</button>
        </div>
        
        <div class="control-group">
            <h3>视图控制</h3>
            <button id="resetView">重置视图</button>
            <button id="togglePoints">切换点云可见性</button>
        </div>
    </div>
    <!-- 加载进度条 -->
    <div id="progressBarContainer" style="position:absolute;left:50%;top:300px;transform:translateX(-50%);width:320px;height:18px;background:rgba(30,40,60,0.8);border-radius:9px;z-index:2000;box-shadow:0 2px 8px #0003;display:none;">
        <div id="progressBar" style="height:100%;width:0%;background:linear-gradient(90deg,#4facfe,#00f2fe);border-radius:9px;"></div>
        <span id="progressText" style="position:absolute;left:0;top:0;width:100%;height:100%;line-height:18px;text-align:center;color:#fff;font-size:13px;pointer-events:none;">0%</span>
    </div>
    
    <div class="info-panel hidden" id="infoPanel">
        <h2>点云信息</h2>
        <div class="info-content">
            <p><strong>当前点云:</strong> <span id="currentCloud">PCD点云</span></p>
            <p><strong>点云位置:</strong> 北京, 中国 (39.9042°N, 116.4074°E)</p>
            <p><strong>渲染点数:</strong> <span class="point-count">100</span> 点</p>
            <p><strong>模型缩放比例:</strong> <span id="pcdScaleDisplay">1000</span></p>
            <p><strong>点云来源:</strong> all_raw_points.pcd</p>
            <p><strong>技术栈:</strong> CesiumJS + Three.js + PCDLoader</p>
            <p><strong>坐标转换:</strong> 直接偏移转换</p>
        </div>
    </div>
    
    <div class="loading hidden" id="loading">
        <div class="spinner"></div>
        <p>加载点云数据中...</p>
    </div>

    <script>
        // 设置Cesium访问令牌
        Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI2Y2M3ZGQ1Yy1hNzkxLTRhNzAtYThmNy1lMTdiYmQyNmNlOGIiLCJpZCI6Mjc3MDEsImlhdCI6MTczNDU3OTUzNH0.L42oi_l2QOJ3At4woANmZ-TBCLaHP5wT0mn31mgdkeg';
        
        // 初始化Cesium Viewer
        const viewer = new Cesium.Viewer('cesiumContainer', {
            baseLayerPicker: false,
            timeline: false,
            animation: false,
            geocoder: false,
            homeButton: false,
            sceneModePicker: false,
            navigationHelpButton: false,
            fullscreenButton: false,
            infoBox: false,
            selectionIndicator: false,
            shadows: true,
            shouldAnimate: true,
            skyAtmosphere: false,
            skyBox: false,
        });
        
        // 异步设置地形
        Cesium.createWorldTerrainAsync().then((terrain) => {
            viewer.terrainProvider = terrain;
        }).catch((error) => {
            console.warn('无法加载世界地形:', error);
        });
        
        // 设置初始视图
        viewer.camera.setView({
            destination: Cesium.Cartesian3.fromDegrees(116.4074, 39.9042, 1500),
            orientation: {
                heading: Cesium.Math.toRadians(0),
                pitch: Cesium.Math.toRadians(-45),
                roll: 0.0
            }
        });
        
        // 创建Three.js场景
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 10e6);
        camera.position.set(0, 0, 0);
        
        // 创建点云组
        const pointCloudGroup = new THREE.Group();
        scene.add(pointCloudGroup);
        
        // 创建渲染器
        const renderer = new THREE.WebGLRenderer({
            alpha: true,
            antialias: true
        });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.domElement.style.position = 'absolute';
        renderer.domElement.style.top = '0px';
        renderer.domElement.style.left = '0px';
        renderer.domElement.style.pointerEvents = 'none';
        renderer.domElement.style.zIndex = '1';
        document.getElementById('cesiumContainer').appendChild(renderer.domElement);
        
        // 点云位置（北京）
        const pointCloudPosition = Cesium.Cartesian3.fromDegrees(116.4074, 39.9042, 0);
        
        // 坐标转换参数
        let coordinateTransform = {
            longitude: 116.4074,
            latitude: 39.9042,
            height: 0
        };
        
        // 渲染参数
        let pcdScale = 1000; // PCD坐标缩放因子
        let pointSize = 1;
        let pointVisible = true;
        let pointWireframe = false;
        
        // 创建PCD点云加载器
        const pcdLoader = new THREE.PCDLoader();
        
        // 加载PCD文件并转换坐标
        function loadPCDFile() {
            // 清除之前的点云
            while(pointCloudGroup.children.length > 0) {
                pointCloudGroup.remove(pointCloudGroup.children[0]);
            }
            // 显示加载状态
            const loading = document.getElementById('loading');
            loading.classList.remove('hidden');
            // 显示进度条
            const progressBarContainer = document.getElementById('progressBarContainer');
            const progressBar = document.getElementById('progressBar');
            const progressText = document.getElementById('progressText');
            progressBar.style.width = '0%';
            progressText.textContent = '0%';
            progressBarContainer.style.display = '';
            // 加载PCD文件
            pcdLoader.load(
                './all_raw_points.pcd',
                function ({geometry}) {
                    // 获取点云数据
                    const positions = geometry.attributes.position.array;
                    const colors = geometry.attributes.color ? geometry.attributes.color.array : null;
                    // 分析PCD坐标范围
                    let minX = Infinity, maxX = -Infinity;
                    let minY = Infinity, maxY = -Infinity;
                    let minZ = Infinity, maxZ = -Infinity;
                    for (let i = 0; i < positions.length; i += 3) {
                        minX = Math.min(minX, positions[i]);
                        maxX = Math.max(maxX, positions[i]);
                        minY = Math.min(minY, positions[i + 1]);
                        maxY = Math.max(maxY, positions[i + 1]);
                        minZ = Math.min(minZ, positions[i + 2]);
                        maxZ = Math.max(maxZ, positions[i + 2]);
                    }
                    // 创建新的几何体用于坐标转换
                    const convertedGeometry = new THREE.BufferGeometry();
                    const convertedPositions = [];
                    const convertedColors = [];
                    // 定义PCD坐标系统的原点（使用可配置的坐标）
                    const pcdOrigin = Cesium.Cartesian3.fromDegrees(
                        coordinateTransform.longitude,
                        coordinateTransform.latitude,
                        coordinateTransform.height // 这里的height就是地表高度偏移
                    );
                    // ENU变换矩阵
                    const enuMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(pcdOrigin);
                    // 转换指定数量的点的坐标（ENU变换）
                    for (let i = 0; i < positions.length; i += 3) {
                        // 1. 取出点云局部坐标，z轴加地面高度偏移
                        const x = positions[i] * pcdScale;
                        const y = positions[i + 1] * pcdScale;
                        const z = positions[i + 2] * pcdScale + coordinateTransform.height; // z轴缩放和偏移在ENU前
                        // 2. 构造局部坐标点
                        const local = new Cesium.Cartesian3(x, y, z);
                        // 3. ENU变换到地心坐标
                        const world = Cesium.Matrix4.multiplyByPoint(enuMatrix, local, new Cesium.Cartesian3());
                        // 4. 存储真实地心坐标
                        convertedPositions.push(world.x, world.y, world.z);
                        // 5. 颜色
                        if (colors) {
                            convertedColors.push(colors[i], colors[i + 1], colors[i + 2]);
                        } else {
                            // 三原色循环
                            const colorIdx = Math.floor(i / 3) % 3;
                            let r = 0, g = 0, b = 0;
                            if (colorIdx === 0) r = 1;
                            else if (colorIdx === 1) g = 1;
                            else if (colorIdx === 2) b = 1;
                            convertedColors.push(r, g, b);
                        }
                    }
                    // 设置转换后的几何体属性
                    convertedGeometry.setAttribute('position', new THREE.Float32BufferAttribute(convertedPositions, 3));
                    convertedGeometry.setAttribute('color', new THREE.Float32BufferAttribute(convertedColors, 3));
                    // 创建点云材质
                    const material = new THREE.PointsMaterial({
                        size: pointSize,
                        vertexColors: true,
                        transparent: true,
                        opacity: 0.8,
                        sizeAttenuation: false,
                        wireframe: pointWireframe
                    });
                    // 创建点云对象
                    const pointCloud = new THREE.Points(convertedGeometry, material);
                    pointCloud.visible = pointVisible;
                    pointCloudGroup.add(pointCloud);
                    // 更新UI
                    document.getElementById('currentCloud').textContent = 'PCD点云';
                    document.querySelector('.point-count').textContent = (positions.length / 3).toString();
                    document.getElementById('pcdScaleDisplay').textContent = pcdScale.toString();
                    // 隐藏加载状态
                    loading.classList.add('hidden');
                    // 隐藏进度条
                    progressBarContainer.style.display = 'none';
                },
                function (progress) {
                    if (progress && progress.total) {
                        let percent = Math.floor(progress.loaded / progress.total * 100);
                        percent = Math.max(0, Math.min(100, percent));
                        progressBar.style.width = percent + '%';
                        progressText.textContent = percent + '%';
                    }
                },
                function (error) {
                    loading.classList.add('hidden');
                    progressBarContainer.style.display = 'none';
                    alert('PCD文件加载失败，请检查文件路径和格式');
                }
            );
        }
        
        // 更新点云大小
        function updatePointSize() {
            pointSize = parseFloat(document.getElementById('pointSize').value);
            pointCloudGroup.traverse((child) => {
                if (child.isPoints) {
                    child.material.size = pointSize;
                }
            });
        }
        // 更新点云可见性
        function togglePoints() {
            pointVisible = !pointVisible;
            pointCloudGroup.traverse((child) => {
                if (child.isPoints) {
                    child.visible = pointVisible;
                }
            });
        }
        
        // 重置视图
        function resetView() {
            viewer.camera.flyTo({
                destination: Cesium.Cartesian3.fromDegrees(116.4074, 39.9042, 1500),
                orientation: {
                    heading: Cesium.Math.toRadians(0),
                    pitch: Cesium.Math.toRadians(-45),
                    roll: 0.0
                },
                duration: 1.5
            });
        }
        
        // 加载点云（模拟加载过程）
        function loadPointCloud() {
            const loading = document.getElementById('loading');
            loading.classList.remove('hidden');
            
            // 模拟加载延迟
            setTimeout(() => {
                loadPCDFile();
                loading.classList.add('hidden');
            }, 1500);
        }
        
        // 事件监听器
        document.getElementById('pointSize').addEventListener('input', () => {
            updatePointSize();
        });
        document.getElementById('pcdScale').addEventListener('input', (e) => {
            pcdScale = parseInt(e.target.value);
            document.getElementById('pcdScaleDisplay').textContent = pcdScale.toString();
        });
        document.getElementById('longitudeOffset').addEventListener('input', (e) => {
            coordinateTransform.longitude = 116.4074 + parseFloat(e.target.value);
        });
        document.getElementById('latitudeOffset').addEventListener('input', (e) => {
            coordinateTransform.latitude = 39.9042 + parseFloat(e.target.value);
        });
        document.getElementById('heightOffset').addEventListener('input', (e) => {
            coordinateTransform.height = parseFloat(e.target.value);
        });
        document.getElementById('loadPCDFile').addEventListener('click', () => loadPCDFile());
        
        document.getElementById('resetView').addEventListener('click', resetView);
        document.getElementById('togglePoints').addEventListener('click', togglePoints);
        
        document.getElementById('toggleControls').addEventListener('click', () => {
            const controlPanel = document.getElementById('controlPanel');
            const infoPanel = document.getElementById('infoPanel');
            const toggleBtn = document.getElementById('toggleControls');
            
            if (controlPanel.classList.contains('hidden')) {
                controlPanel.classList.remove('hidden');
                infoPanel.classList.remove('hidden');
                toggleBtn.textContent = '隐藏控制面板';
            } else {
                controlPanel.classList.add('hidden');
                infoPanel.classList.add('hidden');
                toggleBtn.textContent = '显示控制面板';
            }
        });
        
        // 处理窗口大小变化
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });
        
        // 监听Cesium相机移动事件
        viewer.camera.moveEnd.addEventListener(() => {
            // 当相机停止移动时，更新点云位置
            // updatePointCloudPosition(); // This function is removed, so this line is removed.
        });
        
        // 渲染循环
        function render() {
            // 同步Three.js相机与Cesium相机
            const cesiumCamera = viewer.camera;
            
            // 获取Cesium相机的位置和方向
            const position = cesiumCamera.positionWC;
            const direction = cesiumCamera.directionWC;
            const up = cesiumCamera.upWC;
            
            // 更新Three.js相机位置
            camera.position.set(position.x, position.y, position.z);
            
            // 计算目标点（相机前方）
            const target = new THREE.Vector3(
                position.x + direction.x * 1000,
                position.y + direction.y * 1000,
                position.z + direction.z * 1000
            );
            
            // 设置Three.js相机朝向
            camera.lookAt(target);
            camera.up.set(up.x, up.y, up.z);
            
            // 同步Three.js相机的投影矩阵
            const cesiumProjection = cesiumCamera.frustum.projectionMatrix;
            camera.projectionMatrix.set(
                cesiumProjection[0], cesiumProjection[4], cesiumProjection[8], cesiumProjection[12],
                cesiumProjection[1], cesiumProjection[5], cesiumProjection[9], cesiumProjection[13],
                cesiumProjection[2], cesiumProjection[6], cesiumProjection[10], cesiumProjection[14],
                cesiumProjection[3], cesiumProjection[7], cesiumProjection[11], cesiumProjection[15]
            );
            camera.projectionMatrixInverse.copy(camera.projectionMatrix).invert();
            
            // 旋转立方体作为测试
            // cube.rotation.x += 0.01; // cube is not defined in this script
            // cube.rotation.y += 0.01; // cube is not defined in this script
            
            // 渲染Three.js场景
            renderer.render(scene, camera);
            
            // 请求下一帧
            requestAnimationFrame(render);
        }
        
        
        // 创建初始点云
        loadPCDFile();
        
        // 启动渲染循环
        render();
    </script>
</body>
</html>